/*
 Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
 Original sources are available at www.latestbit.com

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

#ifndef OSSOCKETDEFS_HPP
#define OSSOCKETDEFS_HPP

// OS specific defines
#ifndef WIN32

	#include <sys/types.h>
	#include <sys/socket.h>
	#include <sys/ioctl.h>
	#include <signal.h>
	#include <netinet/in.h>
	#include <arpa/inet.h>
	#include <netdb.h>
	#include <unistd.h>
	#include <sys/time.h>
	#include <memory.h>

	#include <cpputils/CppUtils.hpp>

	namespace smartobjects {
		namespace sockets {
			typedef int Socket;
			typedef fd_set SocketSet;
			typedef sockaddr_in SocketAddr;

			namespace osimpl {

				inline bool initOSSockets() {
					// Disable SIG_PIPE signal.
					signal(SIGPIPE, SIG_IGN);
					return true;
				}

				inline hostent* gethostbyname_r (const char* sName) {

					#ifdef __APPLE__
						int nErrorNum;
						return ::getipnodebyname(sName, AF_INET, AI_DEFAULT, &nErrorNum);
					#else
						int nError = 0;
						char aBuffer[4*1024];
						memset (aBuffer, 0, sizeof (aBuffer));

						hostent* poHostent = (hostent*)malloc(sizeof(hostent));
						if (gethostbyname_r (sName,
											 poHostent,
											 aBuffer,
											 sizeof(aBuffer),
											 &poHostent,
											 &nError) == 0)
							return poHostent;
						else{
							free(poHostent);
							return (struct hostent *) 0;
						}
					#endif
				}

				inline void freehostent ( hostent* poHostEnt) {
					#ifdef __APPLE__
						if(poHostEnt)
							::freehostent(poHostEnt);
					#else
						free(poHostEnt);
					#endif
				}

				inline Socket createSocket(int nType, int nProtocol) {
					return ::socket(AF_INET, nType, nProtocol);
				}

				inline int shutdown(Socket hSocket) {
					return ::shutdown(hSocket, SHUT_RDWR );
				}

				inline int close(Socket hSocket) {
					return ::close ( hSocket );
				}

				inline int bindAddress(Socket hSocket, const SocketAddr& oSocketAddr) {
					return ::bind (
						hSocket,
						(sockaddr*) &oSocketAddr,
						sizeof (oSocketAddr)
					);
				}

				inline int setReuseAddrOption ( Socket hSocket, bool bValue ) {
					int nValue = bValue ? 1:0;
					return setsockopt(hSocket, SOL_SOCKET, SO_REUSEADDR, (void*) &nValue, sizeof(nValue));
				}

				inline int setNoneBlockMode ( Socket hSocket, bool bValue ) {
					int nMode = bValue ? true : false;
					return ioctl ( hSocket, FIONBIO, &nMode );
				}

				inline bool isSuccessConnectResult(int nConnectResult) {
					if(nConnectResult == -1 && errno!=EINPROGRESS)
						return false;
					else
						return true;
				}

				inline bool isSuccessSendResult(int nSendResult) {
					if(nSendResult == -1) {
						#ifndef __APPLE__
						if (errno == EWOULDBLOCK ||
							errno == EINPROGRESS) {
						#else
						if (errno == EAGAIN) {
						#endif
							return true;
						}
						else
							return false;
					}
					else
						return true;
				}


				inline void clearSocketSet(SocketSet& poSet) {
					FD_ZERO ( &poSet );
				}

				inline void insertIntoSocketSet(Socket hSocket, SocketSet& poSet) {
					FD_SET ( hSocket, &poSet );
				}

				inline void removeFromSocketSet(Socket hSocket, SocketSet& poSet) {
					FD_CLR ( hSocket, &poSet );
				}

				inline bool waitEventOnSocketSet (
					Socket nHighestDescriptor,
					SocketSet* poReaders, SocketSet* poWriters,
					SocketSet* poExcepts, unsigned long nTimeoutMSec ) {

					timeval oTimeout = { 0, nTimeoutMSec * 1000 };
					if ( ::select ( nHighestDescriptor, poReaders, poWriters, poExcepts, &oTimeout ) > 0)
						return true;
					return false;
				}

				inline int listenSocket ( Socket hSocket ) {
					return ::listen ( hSocket, SOMAXCONN );
				}

				inline int connectSocket ( Socket hSocket, const SocketAddr& oSockAddr ) {
					return ::connect ( hSocket, (const sockaddr*) &oSockAddr, sizeof(oSockAddr));
				}

				inline Socket acceptClient ( Socket hAcceptorSocket, SocketAddr& poClientAddr) {
					socklen_t nAddrSize = sizeof ( poClientAddr );
					return ::accept ( hAcceptorSocket, (sockaddr*)&poClientAddr, &nAddrSize );
				}

				inline int recv(Socket hSocket, unsigned char* paData, unsigned int nDataLen, int nFlags = 0) {
					return ::recv ( hSocket, (char*)paData, nDataLen, nFlags );
				}

				inline int recvfrom(Socket hSocket, unsigned char* paData, unsigned int nDataLen, SocketAddr& poClientAddr, int nFlags = 0) {
					socklen_t nAddrSize = sizeof ( poClientAddr );
					return ::recvfrom ( hSocket, (char*)paData, nDataLen, nFlags, (sockaddr*)&poClientAddr, &nAddrSize );
				}


				inline int send(Socket hSocket, unsigned char* paData, unsigned int nDataLen, int nFlags = 0) {
					/*int nResult = 0;
					unsigned nShift = 0;
					unsigned nLen = nDataLen;
					do{
						nResult = ::send ( hSocket, (const char*)(paData + nShift), nLen, 0 );
						if(nResult > 0){
							nShift += nResult;
							nLen -= nResult;
						}
					} while(nLen > 0 && (nResult > 0 || isSuccessSendResult(nResult)));
					return (nResult == -1 ? -1 : (int)nDataLen); */
					int nResult = ::send ( hSocket, (const char*)(paData), nDataLen, nFlags );
					if(nResult == -1 && isSuccessSendResult(nResult) ) {
						return 0;
					}
					else
						return nResult;
				}

				inline int sendto(Socket hSocket, unsigned char* paData, unsigned int nDataLen, SocketAddr& poClientAddr, int nFlags = 0) {
					return ::sendto ( hSocket, (const char*)paData, nDataLen, nFlags, (sockaddr*)&poClientAddr, sizeof ( poClientAddr ) );
				}

				inline bool checkSocketError(Socket hSocket) {
					int nError = 0;
					socklen_t nErrorLen = sizeof(nError);
					if (::getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (void*)&nError, &nErrorLen) < 0) {
						return true;
					}
					if(nError)
						return true;
					return false;
				}

			}
		}
	}

#else

#ifndef NOMINMAX
	#define NOMINMAX // Disable microsoft stupid min/max macros which conflicts with std::min/max functions
#endif
	
	#include <winsock2.h>
	#include <ws2tcpip.h>
	#include <windows.h>
	#pragma comment(lib, "Ws2_32.lib")

	#include <cpputils/CppUtils.hpp>

	namespace smartobjects {
		namespace sockets {
			typedef SOCKET Socket;
			typedef fd_set SocketSet;
			typedef sockaddr_in SocketAddr;

			namespace osimpl {

				inline bool initOSSockets() {
					WORD wVersionRequested;
					WSADATA wsaData;
					int err;

					wVersionRequested = MAKEWORD( 2, 2 );

					err = WSAStartup( wVersionRequested, &wsaData );
					if ( err != 0 ) {
						/* Tell the user that we could not find a usable */
						/* WinSock DLL.                                  */
						return false;
					}
					return true;
				}

				inline hostent* gethostbyname_r (const char* sName) {
					hostent* poHostEnt = gethostbyname ( sName );
					return poHostEnt;
				}

				inline void freehostent ( hostent* poHostEnt) {

				}

				inline int shutdown(Socket hSocket) {
					return ::shutdown(hSocket, SD_BOTH );
				}

				inline Socket createSocket(int nType, int nProtocol) {
					return ::socket(AF_INET, nType, nProtocol);
				}

				inline int close(Socket hSocket) {
					return ::closesocket ( hSocket );
				}

				inline int bindAddress(Socket hSocket, const SocketAddr& oSocketAddr) {
					return ::bind (
						hSocket,
						(sockaddr*) &oSocketAddr,
						sizeof (oSocketAddr)
					);
				}

				inline int setReuseAddrOption ( Socket hSocket, bool bValue ) {
					BOOL bWinValue = bValue ? TRUE : FALSE;
					return ::setsockopt(hSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&bWinValue, sizeof(bWinValue));
				}

				inline int setNoneBlockMode ( Socket hSocket, bool bValue ) {
					unsigned int nMode = bValue ? TRUE : FALSE;
					return ::ioctlsocket ( hSocket, FIONBIO, (u_long*)&nMode);
				}

				inline bool isSuccessConnectResult(int nConnectResult) {
					if(nConnectResult == -1) {
						int nError = WSAGetLastError();
						if (nError == WSAEWOULDBLOCK) {
							return true;
						}
						else
							return false;
					}
					else
						return true;
				}

				inline bool isSuccessSendResult(int nSendResult) {
					if(nSendResult == -1) {
						int nError = WSAGetLastError();
						if (nError == WSAEWOULDBLOCK) {
							return true;
						}
						else
							return false;
					}
					else
						return true;
				}


				inline void clearSocketSet(SocketSet& poSet) {
					FD_ZERO ( &poSet );
				}

				inline void insertIntoSocketSet(Socket hSocket, SocketSet& poSet) {
					FD_SET ( hSocket, &poSet );
				}

				inline void removeFromSocketSet(Socket hSocket, SocketSet& poSet) {
					FD_CLR ( hSocket, &poSet );
				}

				inline bool waitEventOnSocketSet (
					Socket nHighestDescriptor,
					SocketSet* poReaders, SocketSet* poWriters,
					SocketSet* poExcepts, unsigned long nTimeoutMSec ) {

					TIMEVAL oTimeout = { 0, nTimeoutMSec * 1000 };
					int nSelectResult = select ( 0, poReaders, poWriters, poExcepts, &oTimeout );
					if ( nSelectResult > 0)
						return true;
					else {
						if(nSelectResult == -1) {
							cpputils::utils::xtSleepMsec ( nTimeoutMSec );
						}
					}
					return false;
				}

				inline int listenSocket ( Socket hSocket ) {
					return ::listen ( hSocket, SOMAXCONN );
				}

				inline int connectSocket ( Socket hSocket, const SocketAddr& oSockAddr ) {
					return ::connect ( hSocket, (const sockaddr*) &oSockAddr, sizeof(oSockAddr));
				}

				inline Socket acceptClient ( Socket hAcceptorSocket, SocketAddr& poClientAddr) {
					int nAddrSize = sizeof ( poClientAddr );
					return ::accept ( hAcceptorSocket, (sockaddr*)&poClientAddr, &nAddrSize );
				}

				inline int recv(Socket hSocket, unsigned char* paData, unsigned int nDataLen, int nFlags = 0) {
					return ::recv ( hSocket, (char*)paData, nDataLen, nFlags );
				}

				inline int recvfrom(Socket hSocket, unsigned char* paData, unsigned int nDataLen, SocketAddr& poClientAddr, int nFlags = 0) {
					int nAddrSize = sizeof ( poClientAddr );
					return ::recvfrom ( hSocket, (char*)paData, nDataLen, nFlags, (sockaddr*)&poClientAddr, &nAddrSize );
				}

				inline int send(Socket hSocket, unsigned char* paData, unsigned int nDataLen, int nFlags = 0) {
					/*
					int nResult = 0;
					unsigned nShift = 0;
					unsigned nLen = nDataLen;
					do{
						nResult = ::send ( hSocket, (const char*)(paData + nShift), nLen, 0 );
						if(nResult > 0){
							nShift += nResult;
							nLen -= nResult;
						}
					} while(nLen > 0 && (nResult > 0 || isSuccessSendResult(nResult)));
					return (nResult == -1 ? -1 : (int)nDataLen);
					*/
					int nResult = ::send ( hSocket, (const char*)(paData), nDataLen, nFlags );
					if(nResult == -1 && isSuccessSendResult(nResult) ) {
						return 0;
					}
					else
						return nResult;
				}

				inline int sendto(Socket hSocket, unsigned char* paData, unsigned int nDataLen, SocketAddr& poClientAddr, int nFlags = 0) {
					return ::sendto ( hSocket, (const char*)paData, nDataLen, nFlags, (sockaddr*)&poClientAddr, sizeof ( poClientAddr ) );
				}

				inline bool checkSocketError(Socket hSocket) {
					return false;
				}

			}
		}
	}

#endif

#endif // OSSOCKETDEFS
